route.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { NextRequest, NextResponse } from "next/server";
  2. import { STORAGE_KEY, internalAllowedWebDavEndpoints } from "../../../constant";
  3. import { getServerSideConfig } from "@/app/config/server";
  4. const config = getServerSideConfig();
  5. const mergedAllowedWebDavEndpoints = [
  6. ...internalAllowedWebDavEndpoints,
  7. ...config.allowedWebDevEndpoints,
  8. ].filter((domain) => Boolean(domain.trim()));
  9. const normalizeUrl = (url: string) => {
  10. try {
  11. return new URL(url);
  12. } catch (err) {
  13. return null;
  14. }
  15. };
  16. async function handle(
  17. req: NextRequest,
  18. { params }: { params: { path: string[] } },
  19. ) {
  20. if (req.method === "OPTIONS") {
  21. return NextResponse.json({ body: "OK" }, { status: 200 });
  22. }
  23. const folder = STORAGE_KEY;
  24. const fileName = `${folder}/backup.json`;
  25. const requestUrl = new URL(req.url);
  26. let endpoint = requestUrl.searchParams.get("endpoint");
  27. let proxy_method = requestUrl.searchParams.get("proxy_method") || req.method;
  28. // Validate the endpoint to prevent potential SSRF attacks
  29. if (
  30. !endpoint ||
  31. !mergedAllowedWebDavEndpoints.some((allowedEndpoint) => {
  32. const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint);
  33. const normalizedEndpoint = normalizeUrl(endpoint as string);
  34. return (
  35. normalizedEndpoint &&
  36. normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname &&
  37. normalizedEndpoint.pathname.startsWith(
  38. normalizedAllowedEndpoint.pathname,
  39. )
  40. );
  41. })
  42. ) {
  43. return NextResponse.json(
  44. {
  45. error: true,
  46. msg: "Invalid endpoint",
  47. },
  48. {
  49. status: 400,
  50. },
  51. );
  52. }
  53. if (!endpoint?.endsWith("/")) {
  54. endpoint += "/";
  55. }
  56. const endpointPath = params.path.join("/");
  57. const targetPath = `${endpoint}${endpointPath}`;
  58. // only allow MKCOL, GET, PUT
  59. if (
  60. proxy_method !== "MKCOL" &&
  61. proxy_method !== "GET" &&
  62. proxy_method !== "PUT"
  63. ) {
  64. return NextResponse.json(
  65. {
  66. error: true,
  67. msg: "you are not allowed to request " + targetPath,
  68. },
  69. {
  70. status: 403,
  71. },
  72. );
  73. }
  74. // for MKCOL request, only allow request ${folder}
  75. if (proxy_method === "MKCOL" && !targetPath.endsWith(folder)) {
  76. return NextResponse.json(
  77. {
  78. error: true,
  79. msg: "you are not allowed to request " + targetPath,
  80. },
  81. {
  82. status: 403,
  83. },
  84. );
  85. }
  86. // for GET request, only allow request ending with fileName
  87. if (proxy_method === "GET" && !targetPath.endsWith(fileName)) {
  88. return NextResponse.json(
  89. {
  90. error: true,
  91. msg: "you are not allowed to request " + targetPath,
  92. },
  93. {
  94. status: 403,
  95. },
  96. );
  97. }
  98. // for PUT request, only allow request ending with fileName
  99. if (proxy_method === "PUT" && !targetPath.endsWith(fileName)) {
  100. return NextResponse.json(
  101. {
  102. error: true,
  103. msg: "you are not allowed to request " + targetPath,
  104. },
  105. {
  106. status: 403,
  107. },
  108. );
  109. }
  110. const targetUrl = targetPath;
  111. const method = proxy_method || req.method;
  112. const shouldNotHaveBody = ["get", "head"].includes(
  113. method?.toLowerCase() ?? "",
  114. );
  115. const fetchOptions: RequestInit = {
  116. headers: {
  117. authorization: req.headers.get("authorization") ?? "",
  118. },
  119. body: shouldNotHaveBody ? null : req.body,
  120. redirect: "manual",
  121. method,
  122. // @ts-ignore
  123. duplex: "half",
  124. };
  125. let fetchResult;
  126. try {
  127. fetchResult = await fetch(targetUrl, fetchOptions);
  128. } finally {
  129. console.log(
  130. "[Any Proxy]",
  131. targetUrl,
  132. {
  133. method: method,
  134. },
  135. {
  136. status: fetchResult?.status,
  137. statusText: fetchResult?.statusText,
  138. },
  139. );
  140. }
  141. return fetchResult;
  142. }
  143. export const PUT = handle;
  144. export const GET = handle;
  145. export const OPTIONS = handle;
  146. export const runtime = "edge";